"""
Dependency Validator for GSM Extension System

This module provides functionality for validating extension dependencies
and checking system requirements.
"""

from typing import Dict, List, Optional, Any
from pathlib import Path
from dataclasses import dataclass, field
import subprocess
import logging
import shutil


@dataclass
class DependencyValidationResult:
    """Result of dependency validation."""
    
    all_satisfied: bool
    satisfied_dependencies: List[str] = field(default_factory=list)
    missing_dependencies: List[str] = field(default_factory=list)
    version_mismatches: List[str] = field(default_factory=list)
    warnings: List[str] = field(default_factory=list)
    
    def add_satisfied(self, dependency: str) -> None:
        """Add a satisfied dependency."""
        self.satisfied_dependencies.append(dependency)
    
    def add_missing(self, dependency: str) -> None:
        """Add a missing dependency."""
        self.missing_dependencies.append(dependency)
        self.all_satisfied = False
    
    def add_version_mismatch(self, dependency: str, required: str, found: str) -> None:
        """Add a version mismatch."""
        mismatch = f"{dependency}: required {required}, found {found}"
        self.version_mismatches.append(mismatch)
        self.all_satisfied = False
    
    def add_warning(self, message: str) -> None:
        """Add a warning message."""
        self.warnings.append(message)


class DependencyValidator:
    """Validates extension dependencies."""
    
    def __init__(self, logger: Optional[logging.Logger] = None):
        """Initialize dependency validator."""
        self._logger = logger or logging.getLogger(__name__)
    
    def validate_dependencies(self, dependencies: Dict[str, str]) -> DependencyValidationResult:
        """
        Validate extension dependencies.
        
        Args:
            dependencies: Dictionary of dependency name -> version requirement
            
        Returns:
            DependencyValidationResult: Validation result
        """
        result = DependencyValidationResult(all_satisfied=True)
        
        for dep_name, version_req in dependencies.items():
            try:
                if self._check_system_dependency(dep_name, version_req, result):
                    result.add_satisfied(dep_name)
                else:
                    result.add_missing(dep_name)
            
            except Exception as e:
                self._logger.warning(f"Error checking dependency {dep_name}: {str(e)}")
                result.add_warning(f"Could not check {dep_name}: {str(e)}")
        
        return result
    
    def _check_system_dependency(self, name: str, version_req: str, 
                                result: DependencyValidationResult) -> bool:
        """Check if a system dependency is available."""
        
        # Check common system dependencies
        if name.lower() in ['mpi', 'openmpi', 'mpich']:
            return self._check_mpi(name, version_req, result)
        elif name.lower() in ['cuda', 'nvidia-cuda']:
            return self._check_cuda(name, version_req, result)
        elif name.lower() in ['hip', 'rocm']:
            return self._check_hip(name, version_req, result)
        elif name.lower() in ['petsc']:
            return self._check_petsc(name, version_req, result)
        elif name.lower() in ['kokkos']:
            return self._check_kokkos(name, version_req, result)
        else:
            # Generic command/library check
            return self._check_generic_dependency(name, version_req, result)
    
    def _check_mpi(self, name: str, version_req: str, 
                  result: DependencyValidationResult) -> bool:
        """Check MPI availability."""
        # Check for mpirun/mpiexec
        mpi_commands = ['mpirun', 'mpiexec']
        
        for cmd in mpi_commands:
            if shutil.which(cmd):
                try:
                    # Try to get version
                    proc = subprocess.run([cmd, '--version'], 
                                        capture_output=True, text=True, timeout=10)
                    if proc.returncode == 0:
                        self._logger.debug(f"Found MPI: {cmd} - {proc.stdout.strip()}")
                        return True
                except Exception:
                    pass
        
        return False
    
    def _check_cuda(self, name: str, version_req: str, 
                   result: DependencyValidationResult) -> bool:
        """Check CUDA availability."""
        # Check for nvcc or nvidia-smi
        cuda_commands = ['nvcc', 'nvidia-smi']
        
        for cmd in cuda_commands:
            if shutil.which(cmd):
                try:
                    # Try to get version
                    proc = subprocess.run([cmd, '--version'], 
                                        capture_output=True, text=True, timeout=10)
                    if proc.returncode == 0:
                        self._logger.debug(f"Found CUDA: {cmd}")
                        return True
                except Exception:
                    pass
        
        return False
    
    def _check_hip(self, name: str, version_req: str, 
                  result: DependencyValidationResult) -> bool:
        """Check HIP/ROCm availability."""
        # Check for hipcc or rocm-smi
        hip_commands = ['hipcc', 'rocm-smi']
        
        for cmd in hip_commands:
            if shutil.which(cmd):
                try:
                    # Try to get version
                    proc = subprocess.run([cmd, '--version'], 
                                        capture_output=True, text=True, timeout=10)
                    if proc.returncode == 0:
                        self._logger.debug(f"Found HIP/ROCm: {cmd}")
                        return True
                except Exception:
                    pass
        
        return False
    
    def _check_petsc(self, name: str, version_req: str, 
                    result: DependencyValidationResult) -> bool:
        """Check PETSc availability."""
        # Check environment variables first
        petsc_dir = Path.cwd() / "external" / "petsc"  # Common location
        if petsc_dir.exists():
            return True
        
        # Check for pkg-config
        try:
            proc = subprocess.run(['pkg-config', '--exists', 'PETSc'], 
                                capture_output=True, timeout=10)
            return proc.returncode == 0
        except Exception:
            pass
        
        return False
    
    def _check_kokkos(self, name: str, version_req: str, 
                     result: DependencyValidationResult) -> bool:
        """Check Kokkos availability."""
        # Check common installation locations
        kokkos_locations = [
            Path.cwd() / "external" / "kokkos",
            Path("/usr/local/include/kokkos"),
            Path("/opt/kokkos"),
        ]
        
        for location in kokkos_locations:
            if location.exists():
                return True
        
        # Check for pkg-config
        try:
            proc = subprocess.run(['pkg-config', '--exists', 'kokkos'], 
                                capture_output=True, timeout=10)
            return proc.returncode == 0
        except Exception:
            pass
        
        return False
    
    def _check_generic_dependency(self, name: str, version_req: str, 
                                 result: DependencyValidationResult) -> bool:
        """Check generic command/library dependency."""
        # First try as a command
        if shutil.which(name):
            return True
        
        # Try common library locations
        lib_locations = [
            Path(f"/usr/lib/lib{name}.so"),
            Path(f"/usr/local/lib/lib{name}.so"),
            Path(f"/opt/lib/lib{name}.so"),
        ]
        
        for location in lib_locations:
            if location.exists():
                return True
        
        return False